/**
 * Web Worker for bsp
 */

importScripts('binFile.js');
importScripts('glMatrix-min.js');

onmessage = function( event )
{
	switch( event.data.type )
	{
		case 'load':
			slBspWorker.load(event.data.url);
			break;
		case 'loadShaders':
			slBspWorker.loadList(event.data.sources);
			break;
		case 'trace':
			slBspWorker.trace(event.data.traceId, event.data.start, event.data.end, event.data.radius, event.data.slide);
			break;
		case 'visibility':
			slBspWorker.buildVisibleList(slBspWorker.getLeaf(event.data.pos));
			break;
		default:
			throw 'Unexpected message type: ' + event.data;
	}
};

slBspWorker = {};

slBspWorker.loadShader = function( sourc )
{

};

//BSP Elements
var planes, nodes, leaves, faces;
var brushes, brushSides;
var leafFaces, leafBrushes;
var visBuffer, visSize;
var shaders; 

// from the html to get the data 
slBspWorker.load = function( url )
{
	var request = new XMLHttpRequest();
	
	if( request )
	{
		request.onreadystatechange = function()
		{
			if( request.readyState == 4 && request.status == 200 )
			{
				slBspWorker.parse( new BinaryFile( request.responseText ) );
			}
			
		};	
		var request_url = url;
		
		request.open('GET', request_url, true );
		request.overrideMimeType('text/plain; charset=x-user-defined');
		request.setRequestHeader('Content-Type', 'text/plain');
		request.send(null);
	}
	
	
};
/*
 * Lump
 * 1 Shader 
 * 2 Planes
 * 3 Nodes
 * 4 Leaves
 * 5 Leaffaces
 * 6 Brusher
 * 7 Models
 * 8 Brushes
 * 9 BrashSides
 * 10 Vertexes
 * 11 Meshverts
 * 13 Faces
 * 14 Lightmaps
 * 16 Visdata 
 */

// parse the binary file
slBspWorker.parse = function( src )
{
	// read the header
	
	var header = slBspWorker.readHeader(src);
	
	if(header.tag != 'IBSP' && header.version != 46) { return; } // Check for appropriate format

	// Read map entities
	slBspWorker.readEntities(header.lumps[0], src);
	
	// Load visual map components
	shaders = slBspWorker.readShaders(header.lumps[1], src);

	var lightmaps = slBspWorker.readLightmaps(header.lumps[14], src);
	
	var verts = slBspWorker.readVerts(header.lumps[10], src);
	var meshVerts = slBspWorker.readMeshVerts(header.lumps[11], src);
	faces = slBspWorker.readFaces(header.lumps[13], src);

	slBspWorker.compileMap(verts, faces, meshVerts, lightmaps, shaders);
	
	// Load bsp components
	planes = slBspWorker.readPlanes(header.lumps[2], src);
	nodes = slBspWorker.readNodes(header.lumps[3], src);
	leaves = slBspWorker.readLeaves(header.lumps[4], src);
	leafFaces = slBspWorker.readLeafFaces(header.lumps[5], src);
	leafBrushes = slBspWorker.readLeafBrushes(header.lumps[6], src);
	brushes = slBspWorker.readBrushes(header.lumps[8], src);
	brushSides = slBspWorker.readBrushSides(header.lumps[9], src);
	var visData = slBspWorker.readVisData(header.lumps[16], src);
	visBuffer = visData.buffer;
	visSize = visData.size;
	
	postMessage({
		type: 'bsp',
		bsp: {
			planes: planes,
			nodes: nodes,
			leaves: leaves,
			leafFaces: leafFaces,
			leafBrushes: leafBrushes,
			brushes: brushes,
			brushSides: brushSides,
			surfaces: shaders,
			visBuffer: visBuffer,
			visSize: visSize
		}
	});	
};

/*
 * stuct bsp_lump
 * {
 * 	uint32 offset;
 * 	uint32 length;
 * }
 *
 *struct bsp_header
 *{
 *	uint32 		magic;
 *	uint32		version;
 * 	bsp_lump	lump[19]
 *} 
 */
slBspWorker.readHeader = function( src )
{
	var header = {
			magic: src.readString(4),
			version: src.readULong(),
			lumps: new Array()
	};
	
	for( var i = 0; i < 17; ++i )
	{
		var lump = {
				offset: src.readULong(),
				length: src.readULong()
		};
		header.lumps.push(lump);
	}
	
	return header;
};

/*
*int texture	Texture index.
*int effect	Index into lump 12 (Effects), or -1.
*int type	Face type. 1=polygon, 2=patch, 3=mesh, 4=billboard
*int vertex	Index of first vertex.
*int n_vertexes	Number of vertices.
*int meshvert	Index of first meshvert.
*int n_meshverts	Number of meshverts.
*int lm_index	Lightmap index.
*int[2] lm_start	Corner of this face's lightmap image in lightmap.
*int[2] lm_size	Size of this face's lightmap image in lightmap.
*float[3] lm_origin	World space origin of lightmap.
*float[2][3] lm_vecs	World space lightmap s and t unit vectors.
*float[3] normal	Surface normal.
*int[2] size	Patch dimensions.
 */
slBspWorker.readFaces = function( lump, src )
{
	var face_count = lump.length / 104;
	var faces = new Array();
	
	src.seek( lump.offset );
	
	for( var i = 0; i < face_count; ++i )
	{
		var face = {
				shader: src.readLong(),
				effect: src.readLong(),
				type: src.readLong(),
				vertex: src.readLong(),
				vertCount: src.readLong(),
				meshVert: src.readLong(),
				meshVertCount:src.readLong(),
				lightmap: src.readLong(),
				lmStatr: [ src.readLong(), src.readLong() ],
				lmSize: [ src.readLong(), src.readLong() ],
				lmOrigin: [ src.readFloat(), src.readFloat(), src.readFloat() ],
				lmVecs: [[ src.readFloat(), src.readFloat(), src.readFloat() ],
				         [ src.readFloat(), src.readFloat(), src.readFloat()]],
				normal: [ src.readFloat(), src.readFloat(), src.readFloat() ],
				size: [src.readLong(), src.readLong()],
				indexOffset: -1
		};
		faces.push(face);
	}
	return faces;
};

/*
 * struct bsp_plane
 * {
 *	vector3f normal;
 * 	float 	distance; 	
 * }
 */

slBspWorker.readPlanes = function( lump, src )
{
	var count = lump.lenght / 16;
	var planes = new Array();
	
	src.seek( lump.offset );
	for ( var i = 0; i < count; ++i )
	{
		var plane = {
				normal : [ src.readFloat(), src.readFloat(), src.readFloat() ],
				distance : src.readFloat()
		};
		planes.push( plane );
	}
	return planes;
};

/*
 * struct bsp_node
 * { 
 *	uint32 plane;
 * 	uint32 front_child;
 * 	uint32 back_child;
 * 
 *  vector4f bbox_min;
 *  vector3f bbox_max;
 *  
 *  uint16 first_face;
 *  uint16 num_faces;
 * }
 */
slBspWorker.readNodes = function( lump, src )
{
	var count = lump.length / 36;
	var nodes = new Array();
		
	src.seek( lump.offset );
	
	for ( var i = 0; i < count; ++i )
	{
		var node = {
				plane: src.readLong(),
				children: [ src.readLong(), src.readLong()],
				min: [ src.readLong(), src.readLong(), src.readLong() ],
				max: [ src.readLong(), src.readLong(), src.readLong() ]
		};
		nodes.push( node );
	}
	return nodes;
};
/*
 * struct bsp_leaf
 * {
 * 	uint32 brush_or;
 *  uint16 cluster;
 *  uint16 area;
 *   
 *  vector3f bbox_min;
 *  vector3f bbox_max;
 *  
 *  uint16 first_leaf_face;
 *  uint16 num_leaf_face;
 *  
 *  uint16 first_leaf_brush;
 *  uint16 num_leaf_brushes;
 * }
 */

slBspWorker.readLeaves = function( lump, src )
{
	var count = lump.length / 48;
	var leaves = new Array();
	
	src.seek( lump.offset );
	for( var i = 0; i < count; ++i )
	{
		var leaf = {
				cluster: src.readLong(),
				area: src.readLong(),
				min:[ src.readLong(), src.readLong(), src.readLong() ],
				max:[ src.readLong(), src.readLong(), src.readLong() ],
				leafFace: src.readLong(),
				leafFaceCount: src.readLong(),
				leafBrush: src.readLong(),
				leafBrushCount: src.readLong()
		};
		leaves.push( leaf );
	}
	return leaves;
};

//Read all shader structures
slBspWorker.readShaders = function(lump, src) {
	var count = lump.length / 72;
	var elements = new Array();
	
	src.seek(lump.offset);
	for(var i = 0; i < count; ++i) {
		var shader = {
			shaderName: src.readString(64),
			flags: src.readLong(),
			contents: src.readLong(),
			shader: null,
			faces: new Array(),
			indexOffset: 0,
			elementCount: 0,
			visible: true
		};
		
		elements.push(shader);
	}
	
	return elements;
};

/*
 * 
 */
slBspWorker.readLeafFaces = function( lump, src )
{
	var count = lump.length / 4;
	var leaf_faces = new Array();
	
	src.seek( lump.offset );
	
	for( var i = 0; i < count; ++i )
	{
		leaf_faces.push( src.readLong() );
	}
	
	return leaf_faces;
	
};

slBspWorker.readVerts = function(lump, src) {
	var count = lump.length/44;
	var elements = new Array();
	
	src.seek(lump.offset);
	for(var i = 0; i < count; ++i) {
		elements.push({
			pos: [ src.readFloat(), src.readFloat(), src.readFloat() ],
			texCoord: [ src.readFloat(), src.readFloat() ],
			lmCoord: [ src.readFloat(), src.readFloat() ],
			lmNewCoord: [ 0, 0 ],
			normal: [ src.readFloat(), src.readFloat(), src.readFloat() ],
			color: slBspWorker.brightnessAdjustVertex(slBspWorker.colorToVec(src.readULong()), 4.0)
		});
	}

	return elements;
};

/*
 */
slBspWorker.readBrushes = function( lump, src )
{
	var count = lump.length / 12;
	var brushes = new Array();
	
	src.seek( lump.offset );
	for( var i = 0; i < count; ++ i)
	{
		var brush = {
				brushSide: src.readLong(),
				brushSideCount: src.readLong(),
				shader: src.readLong()
		};
		
		brushes.push( brush );
	}
	return brushes;
};

slBspWorker.readLeafBrushes = function( lump, src )
{
	var count = lump.length / 4;
	var brushes = new Array();
	
	src.seek( lump.offset );
	for( var i = 0; i < count; ++i )
	{
		brushes.push( src.readLong() );
	}
	return brushes;
	
};

slBspWorker.readBrushSides = function( lump, src )
{
	var count = lump.length / 4;
	var BrushSides = new Array();
	
	src.seek( lump.offset );
	
	for ( var i = 0; i < count; ++i )
	{
		var side = {
				plane: src.readLong(),
				shader: src.readLong()
		};
		BrushSides.push( side );
	}
};

slBspWorker.readVisData = function( lump, src )
{
	src.seek( lump.offset );
	var count = src.readLong();
	var size = src.readLong();
	
	var byteCount = count * size;
	var elements = new Array(byteCount);
	
	for ( var i = 0; i < count; ++i )
	{
		elements[i] = src.readUByte();
	}
	
	return {
		buffer:elements,
		size: size
	};
};

slBspWorker.readMeshVerts = function(lump, src) {
	var count = lump.length/4;
	var meshVerts = new Array();
	
	src.seek(lump.offset);
	for(var i = 0; i < count; ++i) {
		meshVerts.push(src.readLong());
	}
	
	return meshVerts;
};

// 
slBspWorker.readShader = function( lump, src )
{
	var count = lump.lenght / 72;
	var shaders = new Array();
	
	src.seek( lump.offset );
	for( var i = 0; i < count; ++i )
	{
		var shader = {
				shaderName:src.readString(64),
				flags:src.readLong(),
				contents:src.readLong(),
				shader: null,
				faces: new Array(),
				indexOffset: 0, 
				elementCount: 0,
				visible: true
		};
		
		shaders.push( shader );
	}
	return shaders;
};

//Read all lightmaps
slBspWorker.readLightmaps = function(lump, src) {
	
	var lightmapSize = 128 * 128;
	var count = lump.length / (lightmapSize*3);
	
	var gridSize = 2;
	
	while(gridSize * gridSize < count) {
		gridSize *= 2;
	}

	var textureSize = gridSize * 128;
	
	var xOffset = 0;
	var yOffset = 0;
	
	var lightmaps = new Array();
	var lightmapRects = new Array();
	var rgb = [ 0, 0, 0 ];
	
	src.seek(lump.offset);
	
	for(var i = 0; i < count; ++i) {
		var elements = new Array(lightmapSize*4);
		
		for(var j = 0; j < lightmapSize*4; j+=4) {
			rgb[0] = src.readUByte();
			rgb[1] = src.readUByte();
			rgb[2] = src.readUByte();
			
			slBspWorker.brightnessAdjust(rgb, 4.0);
			
			elements[j] = rgb[0];
			elements[j+1] = rgb[1];
			elements[j+2] = rgb[2];
			elements[j+3] = 255;
		}
		
		lightmaps.push({
			x: xOffset, y: yOffset,
			width: 128, height: 128,
			bytes: elements
		});
		
		lightmapRects.push({
			x: xOffset/textureSize,
			y: yOffset/textureSize,
			xScale: 128/textureSize,
			yScale: 128/textureSize
		});
		
		xOffset += 128;
		if(xOffset >= textureSize) {
			yOffset += 128;
			xOffset = 0;
		}
	}

	// Send the lightmap data back to the render thread
	postMessage({
		type: 'lightmap',
		size: textureSize,
		lightmaps: lightmaps
	});
	
	return lightmapRects;
};

//Read all entity structures
slBspWorker.readEntities = function(lump, src) {
	src.seek(lump.offset);
	var entities = src.readString(lump.length);
	
	var elements = {
		targets: {}
	};
	
	entities.replace(/\{([^}]*)\}/mg, function($0, entitySrc) {
		var entity = {
			classname: 'unknown'
		};
		entitySrc.replace(/"(.+)" "(.+)"$/mg, function($0, key, value) {
			switch(key) {
				case 'origin':
					value.replace(/(.+) (.+) (.+)/, function($0, x, y, z) {
						entity[key] = [
							parseFloat(x), 
							parseFloat(y), 
							parseFloat(z)
						];
					});
					break;
				case 'angle':
					entity[key] = parseFloat(value);
					break;
				default:
					entity[key] = value;
					break;
			}
		});
		
		if(entity['targetname']) {
			elements.targets[entity['targetname']] = entity;
		}
		
		if(!elements[entity.classname]) { elements[entity.classname] = new Array(); }
		elements[entity.classname].push(entity);
	});
	
	// Send the compiled vertex/index data back to the render thread
	postMessage({
		type: 'entities',
		entities: elements
	});
};

slBspWorker.colorToVec = function( color )
{
	return[
	       (color & 0xFF )/0xFF,
	       ((color & 0xFF00) >> 8) / 0xFF,
	       ((color & 0xFF0000) >> 16) / 0xFF,
	       1
	       ];
};

//Scale up an RGB color
slBspWorker.brightnessAdjust = function(color, factor) {
	var scale = 1.0, temp = 0.0;
	
	color[0] *= factor;
	color[1] *= factor;
	color[2] *= factor;
	
	if(color[0] > 255 && (temp = 255/color[0]) < scale) { scale = temp; }
	if(color[1] > 255 && (temp = 255/color[1]) < scale) { scale = temp; }
	if(color[2] > 255 && (temp = 255/color[2]) < scale) { scale = temp; }
	
	color[0] *= scale;
	color[1] *= scale;
	color[2] *= scale;
	
	return color;
};

slBspWorker.brightnessAdjustVertex = function(color, factor) {
	var scale = 1.0, temp = 0.0;
	
	color[0] *= factor;
	color[1] *= factor;
	color[2] *= factor;
	
	if(color[0] > 1 && (temp = 1/color[0]) < scale) { scale = temp; }
	if(color[1] > 1 && (temp = 1/color[1]) < scale) { scale = temp; }
	if(color[2] > 1 && (temp = 1/color[2]) < scale) { scale = temp; }
	
	color[0] *= scale;
	color[1] *= scale;
	color[2] *= scale;
	
	return color;
};


//
//Compile the map into a stream of WebGL-compatible data
//

slBspWorker.compileMap = function(verts, faces, meshVerts, lightmaps, shaders) {
	
	var tesselationLevel = 5;
	// Per-face operations
	for(var i = 0; i < faces.length; ++i) {
		var face = faces[i];
		
		if(face.type==1 || face.type==2 || face.type==3) {
			// Add face to the appropriate texture face list
			var shader = shaders[face.shader];
			shader.faces.push(face);
			var lightmap = lightmaps[face.lightmap];
			
			if(!lightmap) {
				lightmap = lightmaps[0];
			}
			
			if(face.type==1 || face.type==3) {
				shader.geomType = face.type;
				// Transform lightmap coords to match position in combined texture
				for(var j = 0; j < face.meshVertCount; ++j) {
					var vert = verts[face.vertex + meshVerts[face.meshVert + j]];

					vert.lmNewCoord[0] = (vert.lmCoord[0] * lightmap.xScale) + lightmap.x;
					vert.lmNewCoord[1] = (vert.lmCoord[1] * lightmap.yScale) + lightmap.y;
				}
			} else {
				// Build Bezier curve
				slBspWorker.tesselate(face, verts, meshVerts, tesselationLevel);
				for(var j = 0; j < face.vertCount; ++j) {
					var vert = verts[face.vertex + j];
					
					vert.lmNewCoord[0] = (vert.lmCoord[0] * lightmap.xScale) + lightmap.x;
					vert.lmNewCoord[1] = (vert.lmCoord[1] * lightmap.yScale) + lightmap.y;
				}
			}
		}
	}

	// Compile vert list
	var vertices = new Array(verts.length*10);
	var offset = 0;
	for(var i = 0; i < verts.length; ++i) {
		var vert = verts[i];
		
		vertices[offset++] = vert.pos[0];
		vertices[offset++] = vert.pos[1];
		vertices[offset++] = vert.pos[2];
		
		vertices[offset++] = vert.texCoord[0];
		vertices[offset++] = vert.texCoord[1];
		
		vertices[offset++] = vert.lmNewCoord[0];
		vertices[offset++] = vert.lmNewCoord[1];
		
		vertices[offset++] = vert.normal[0];
		vertices[offset++] = vert.normal[1];
		vertices[offset++] = vert.normal[2];
		
		vertices[offset++] = vert.color[0];
		vertices[offset++] = vert.color[1];
		vertices[offset++] = vert.color[2];
		vertices[offset++] = vert.color[3];
	}
	
	// Compile index list
	var indices = new Array();
	for(var i = 0; i <  shaders.length; ++i) {
		var shader = shaders[i];
		if(shader.faces.length > 0) {
			shader.indexOffset = indices.length * 2; // Offset is in bytes
			
			for(var j = 0; j < shader.faces.length; ++j) {
				var face = shader.faces[j];
				face.indexOffset = indices.length * 2;
				for(var k = 0; k < face.meshVertCount; ++k) {
					indices.push(face.vertex + meshVerts[face.meshVert + k]);
				}
				shader.elementCount += face.meshVertCount;
			}
		}
		shader.faces = null; // Don't need to send this to the render thread.
	}
	// Send the compiled vertex/index data back to the render thread
	postMessage({
		type: 'geometry',
		vertices: vertices,
		indices: indices,
		surfaces: shaders
	});
};

//
//Curve Tesselation
//

slBspWorker.getCurvePoint3 = function(c0, c1, c2, dist) {
	var b = 1.0 - dist;
	
	return vec3.add(
		vec3.add(
			vec3.scale(c0, (b*b), [0, 0, 0]), 
			vec3.scale(c1, (2*b*dist), [0, 0, 0])
		), 
		vec3.scale(c2, (dist*dist), [0, 0, 0])
	);
};

//This is kinda ugly. Clean it up at some point?
slBspWorker.getCurvePoint2 = function(c0, c1, c2, dist) {
	var b = 1.0 - dist;
	
	c30 = [c0[0], c0[1], 0];
	c31 = [c1[0], c1[1], 0];
	c32 = [c2[0], c2[1], 0];
	
	var res = vec3.add(
		vec3.add(
			vec3.scale(c30, (b*b), [0, 0, 0]), 
			vec3.scale(c31, (2*b*dist), [0, 0, 0])
		), 
		vec3.scale(c32, (dist*dist), [0, 0, 0])
	);
	
	return [res[0], res[1]];
};

slBspWorker.tesselate = function(face, verts, meshVerts, level) {
	var off = face.vertex;
	var count = face.vertCount;
	
	var L1 = level + 1;
	
	face.vertex = verts.length;
	face.meshVert = meshVerts.length;
	
	face.vertCount = 0;
	face.meshVertCount = 0;
	
	for(var py = 0; py < face.size[1]-2; py += 2) {
		for(var px = 0; px < face.size[0]-2; px += 2) {
			
			var rowOff = (py*face.size[0]);
			
			// Store control points
			var c0 = verts[off+rowOff+px], c1 = verts[off+rowOff+px+1], c2 = verts[off+rowOff+px+2];
			rowOff += face.size[0];
			var c3 = verts[off+rowOff+px], c4 = verts[off+rowOff+px+1], c5 = verts[off+rowOff+px+2];
			rowOff += face.size[0];
			var c6 = verts[off+rowOff+px], c7 = verts[off+rowOff+px+1], c8 = verts[off+rowOff+px+2];
			
			var indexOff = face.vertCount;
			face.vertCount += L1 * L1;
			
			// Tesselate!
			for(var i = 0; i < L1; ++i)
			{
				var a = i / level;
				
				var pos = slBspWorker.getCurvePoint3(c0.pos, c3.pos, c6.pos, a);
				var lmCoord = slBspWorker.getCurvePoint2(c0.lmCoord, c3.lmCoord, c6.lmCoord, a);
				var texCoord = slBspWorker.getCurvePoint2(c0.texCoord, c3.texCoord, c6.texCoord, a);
				var color = slBspWorker.getCurvePoint3(c0.color, c3.color, c6.color, a);
				
				var vert = {
					pos: pos,
					texCoord: texCoord,
					lmCoord: lmCoord,
					color: [color[0], color[1], color[2], 1],
					lmNewCoord: [ 0, 0 ],
					normal: [0, 0, 1]
				};
				
				verts.push(vert);
			}
			
			for(var i = 1; i < L1; i++)
			{
				var a = i / level;
				
				var pc0 = slBspWorker.getCurvePoint3(c0.pos, c1.pos, c2.pos, a);
				var pc1 = slBspWorker.getCurvePoint3(c3.pos, c4.pos, c5.pos, a);
				var pc2 = slBspWorker.getCurvePoint3(c6.pos, c7.pos, c8.pos, a);
				
				var tc0 = slBspWorker.getCurvePoint3(c0.texCoord, c1.texCoord, c2.texCoord, a);
				var tc1 = slBspWorker.getCurvePoint3(c3.texCoord, c4.texCoord, c5.texCoord, a);
				var tc2 = slBspWorker.getCurvePoint3(c6.texCoord, c7.texCoord, c8.texCoord, a);
				
				var lc0 = slBspWorker.getCurvePoint3(c0.lmCoord, c1.lmCoord, c2.lmCoord, a);
				var lc1 = slBspWorker.getCurvePoint3(c3.lmCoord, c4.lmCoord, c5.lmCoord, a);
				var lc2 = slBspWorker.getCurvePoint3(c6.lmCoord, c7.lmCoord, c8.lmCoord, a);
				
				var cc0 = slBspWorker.getCurvePoint3(c0.color, c1.color, c2.color, a);
				var cc1 = slBspWorker.getCurvePoint3(c3.color, c4.color, c5.color, a);
				var cc2 = slBspWorker.getCurvePoint3(c6.color, c7.color, c8.color, a);
				
				for(j = 0; j < L1; j++)
				{
					var b = j / level;
					
					var pos = slBspWorker.getCurvePoint3(pc0, pc1, pc2, b);
					var texCoord = slBspWorker.getCurvePoint2(tc0, tc1, tc2, b);
					var lmCoord = slBspWorker.getCurvePoint2(lc0, lc1, lc2, b);
					var color = slBspWorker.getCurvePoint3(cc0, cc1, cc2, a);
					
					var vert = {
						pos: pos,
						texCoord: texCoord,
						lmCoord: lmCoord,
						color: [color[0], color[1], color[2], 1],
						lmNewCoord: [ 0, 0 ],
						normal: [0, 0, 1]
					};
				
					verts.push(vert);
				}
			}
			
			face.meshVertCount += level * level * 6;
			
			for(var row = 0; row < level; ++row) {
				for(var col = 0; col < level; ++col) {
					meshVerts.push(indexOff + (row + 1) * L1 + col);
					meshVerts.push(indexOff + row * L1 + col);
					meshVerts.push(indexOff + row * L1 + (col+1));
					
					meshVerts.push(indexOff + (row + 1) * L1 + col);
					meshVerts.push(indexOff + row * L1 + (col+1));
					meshVerts.push(indexOff + (row + 1) * L1 + (col+1));
				}
			}
	
		}
	}
};

//
//BSP Collision Detection
//

slBspWorker.trace = function(traceId, start, end, radius, slide) {
	if(!radius) { radius = 0; }
	if(!slide) { slide = false; }
	
	if (!brushSides) { return end; }
	
	var output = {
		startsOut: true,
		allSolid: false,
		plane: null,
		fraction: 1
	};
	
	slBspWorker.traceNode(0, 0, 1, start, end, radius, output);
	
	if(output.fraction != 1) { // collided with something
		if(slide && output.plane) {
			var endDist = Math.abs(vec3.dot( end, output.plane.normal ) - (output.plane.distance + radius + 0.03125));
			for (var i = 0; i < 3; i++) {
				end[i] = end[i] + endDist * (output.plane.normal[i]);
			}
		} else {
			for (var i = 0; i < 3; i++) {
				end[i] = start[i] + output.fraction * (end[i] - start[i]);
			}
		}
	}
	
	postMessage({
		type: 'trace',
		traceId: traceId,
		end: end
	});
};

slBspWorker.traceNode = function(nodeIdx, startFraction, endFraction, start, end, radius, output) {
	if (nodeIdx < 0) { // Leaf node?
		var leaf = leaves[-(nodeIdx + 1)];
		for (var i = 0; i < leaf.leafBrushCount; i++) {
			var brush = brushes[leafBrushes[leaf.leafBrush + i]];
			var shader = shaders[brush.shader];
			if (brush.brushSideCount > 0 && (shader.contents & 1)) {
				slBspWorker.traceBrush(brush, start, end, radius, output);
			}
		}
		return;
	}
	
	// Tree node
	var node = nodes[nodeIdx];
	var plane = planes[node.plane];
	
	var startDist = vec3.dot(plane.normal, start) - plane.distance;
	var endDist = vec3.dot(plane.normal, end) - plane.distance;
	
	if (startDist >= radius && endDist >= radius) {
		slBspWorker.traceNode(node.children[0], startFraction, endFraction, start, end, radius, output );
	} else if (startDist < -radius && endDist < -radius) {
		slBspWorker.traceNode(node.children[1], startFraction, endFraction, start, end, radius, output );
	} else {
		var side;
		var fraction1, fraction2, middleFraction;
		var middle = [0, 0, 0];

		if (startDist < endDist) {
			side = 1; // back
			var iDist = 1 / (startDist - endDist);
			fraction1 = (startDist - radius + 0.03125) * iDist;
			fraction2 = (startDist + radius + 0.03125) * iDist;
		} else if (startDist > endDist) {
			side = 0; // front
			var iDist = 1 / (startDist - endDist);
			fraction1 = (startDist + radius + 0.03125) * iDist;
			fraction2 = (startDist - radius - 0.03125) * iDist;
		} else {
			side = 0; // front
			fraction1 = 1;
			fraction2 = 0;
		}
		
		if (fraction1 < 0) fraction1 = 0;
		else if (fraction1 > 1) fraction1 = 1;
		if (fraction2 < 0) fraction2 = 0;
		else if (fraction2 > 1) fraction2 = 1;
		
		middleFraction = startFraction + (endFraction - startFraction) * fraction1;
		
		for (var i = 0; i < 3; i++) {
			middle[i] = start[i] + fraction1 * (end[i] - start[i]);
		}
		
		slBspWorker.traceNode(node.children[side], startFraction, middleFraction, start, middle, radius, output );
		
		middleFraction = startFraction + (endFraction - startFraction) * fraction2;
		
		for (var i = 0; i < 3; i++) {
			middle[i] = start[i] + fraction2 * (end[i] - start[i]);
		}
		
		slBspWorker.traceNode(node.children[side==0?1:0], middleFraction, endFraction, middle, end, radius, output );  
	}
};

slBspWorker.traceBrush = function(brush, start, end, radius, output) {
	var startFraction = -1;
	var endFraction = 1;
	var startsOut = false;
	var endsOut = false;
	var collisionPlane = null;
	
	for (var i = 0; i < brush.brushSideCount; i++) {
		var brushSide = brushSides[brush.brushSide + i];
		var plane = planes[brushSide.plane];
		
		var startDist = vec3.dot( start, plane.normal ) - (plane.distance + radius);
		var endDist = vec3.dot( end, plane.normal ) - (plane.distance + radius);

		if (startDist > 0) startsOut = true;
		if (endDist > 0) endsOut = true;

		// make sure the trace isn't completely on one side of the brush
		if (startDist > 0 && endDist > 0) { return; }
		if (startDist <= 0 && endDist <= 0) { continue; }

		if (startDist > endDist) { // line is entering into the brush
			var fraction = (startDist - 0.03125) / (startDist - endDist);  
			if (fraction > startFraction) {
				startFraction = fraction;
				collisionPlane = plane;
			}
		} else { // line is leaving the brush
			var fraction = (startDist + 0.03125) / (startDist - endDist); 
			if (fraction < endFraction)
				endFraction = fraction;
		}
	}
	
	if (startsOut == false) {
		output.startsOut = false;
		if (endsOut == false)
			output.allSolid = true;
		return 
	}

	if (startFraction < endFraction) {
		if (startFraction > -1 && startFraction < output.fraction) {
			output.plane = collisionPlane;
			if (startFraction < 0)
				startFraction = 0;
			output.fraction = startFraction;
		}
	}
	
	return 
};

//
//Visibility Checking
//

var lastLeaf = -1;

slBspWorker.checkVis = function(visCluster, testCluster) {
	if(visCluster == testCluster || visCluster == -1) { return true; }
	var i = (visCluster * visSize) + (testCluster >> 3);
	var visSet = visBuffer[i];
	return (visSet & (1 << (testCluster & 7)) != 0);
};

slBspWorker.getLeaf = function(pos) {
	var index = 0;
	
	var node = null;
	var plane = null;
	var distance = 0;

	while (index >= 0) {
		node = nodes[index];
		plane = planes[node.plane];
		distance = vec3.dot(plane.normal, pos) - plane.distance;

		if (distance >= 0) {
			index = node.children[0];
		} else {
			index = node.children[1];
		}
	}

	return -(index+1);
};

slBspWorker.buildVisibleList = function(leafIndex) {
	// Determine visible faces
	if(leafIndex == lastLeaf) { return; }
	lastLeaf = leafIndex;
	
	var curLeaf = leaves[leafIndex];
	
	var visibleShaders = new Array(shaders.length);
	
	for(var i = 0; i < leaves.length; ++i) {
		var leaf = leaves[i];
		if(slBspWorker.checkVis(curLeaf.cluster, leaf.cluster)) {
			for(var j = 0; j < leaf.leafFaceCount; ++j) {
				var face = faces[leafFaces[[j + leaf.leafFace]]];
				if(face) {
					visibleShaders[face.shader] = true;
				}
			}
		}
	}
	
	var ar = new Array(visSize);
	
	for(var i = 0; i < visSize; ++i) {
		ar[i] = visBuffer[(curLeaf.cluster * visSize) + i];
	}
	
	postMessage({
		type: 'visibility',
		visibleSurfaces: visibleShaders
	});
};


